package job

import (
	"booksmart/book/internal/domain"
	"booksmart/book/internal/service"
	logger2 "booksmart/book/pkg/logger"
	"context"
	"fmt"
	"golang.org/x/sync/semaphore"
	"time"
)

type Executor interface {
	Name() string
	Exec(ctx context.Context, j domain.Job) error
}

type LocalExecutor struct {
	funcs map[string]func(ctx context.Context, j domain.Job) error
}

func NewLocalExecutor() *LocalExecutor {
	return &LocalExecutor{funcs: make(map[string]func(ctx context.Context, j domain.Job) error)}
}

func (l *LocalExecutor) Register(name string, fn func(ctx context.Context, j domain.Job) error) {
	l.funcs[name] = fn
}

func (l *LocalExecutor) Name() string {
	return "ranking local"
}

func (l *LocalExecutor) Exec(ctx context.Context, j domain.Job) error {
	fn, ok := l.funcs[j.Name]
	if !ok {
		return fmt.Errorf("没有注册")
	}
	return fn(ctx, j)
}

type Scheduler struct {
	execs map[string]Executor
	svc   service.JobService
	l     logger2.LoggerV1
	limt  *semaphore.Weighted
}

func NewScheduler(svc service.JobService, l logger2.LoggerV1) *Scheduler {
	return &Scheduler{svc: svc,
		l:     l,
		execs: make(map[string]Executor),
		limt:  semaphore.NewWeighted(10)}
}

func (s *Scheduler) Register(exec Executor) {
	s.execs[exec.Name()] = exec
}

func (s *Scheduler) Name() string {
	return "mysql_ranking_job"
}

// 这里可以考虑增加，负载均衡功能，挑选负载最小的一台节点，进行调度。
func (s *Scheduler) Run() error {
	//信号量
	for {
		err2 := s.limt.Acquire(context.Background(), 1)
		if err2 != nil {
			return err2
		}
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		job, err := s.svc.Preempt(ctx)
		if err != nil {
			s.l.Error("抢占任务失败", logger2.Error(err))
		}
		//执行
		executor, ok := s.execs[job.Executor]
		if !ok {
			s.l.Error("未找到对应的执行器")
			continue
		}
		go func() {
			defer func() {
				s.limt.Release(1)
				err1 := job.CancelFunc()
				if err1 != nil {
					s.l.Error("释放锁失败", logger2.Error(err1), logger2.String("jid", job.Id))
				}
			}()
			err1 := executor.Exec(ctx, job)
			if err1 != nil {
				//这里可以进行重试
				s.l.Error("任务执行失败", logger2.Error(err1))
			}
			err1 = s.svc.ResetNextTime(ctx, job)
			if err1 != nil {
				s.l.Error("设置下一次执行时间失败", logger2.Error(err1))
			}
		}()
	}
}
